import java.io.*;
import java.net.*;
import java.util.StringTokenizer;
import java.sql.Time;

public class ftp_server
{
	static int serverPort = 2121;
	static ftp_server forFunc = new ftp_server();			//for calling function only =.=
	static ServerSocket serverSocket;
	

	public static void main(String argv[]) throws IOException
	{
		serverSocket = new ServerSocket(serverPort);		//create a server socket for listening 
		System.out.println("Server is running");

		boolean listening = true;
		while (listening)
		{
			ServerThread handler = new ftp_server().new ServerThread(serverSocket.accept());
			handler.start();
			//new ftp_server().new ServerThread(serverSocket.accept()).start();
		}

		//----------------------------------------------------
		serverSocket.close();
	}

	//for reading command form client
	public File readCommand(Socket serverSocket, String temp, BufferedReader inFromClient, DataOutputStream outToClient, File curDir) throws IOException
	{
		// for returning 
		File TcurDir = curDir;

		// ------------ get action --------------------
		System.out.println(temp);

		String action = new String("temp");	//meaningless 		
		String parameter = null;

		//System.out.println(temp);

		StringTokenizer actionC = new StringTokenizer(temp, " ");
		int num = actionC.countTokens();
		action = actionC.nextToken();
		System.out.println(num);
		if (num > 1)
		{
			parameter = actionC.nextToken();

			while (actionC.hasMoreTokens())
			{
				System.out.println(".");
				parameter = new String(parameter + " " + actionC.nextToken());
			}
			System.out.println(parameter);
		}


		if (action.equals("PASV"))
		{
			System.out.println("PASV");
			if (new ftp_server().PASV(serverSocket, inFromClient, outToClient, curDir))
			{
				System.out.println("226 Transfer OK");
				outToClient.writeBytes("226 Transfer OK\r\n");
			}
		}
		else if (action.equals("PWD"))
			new ftp_server().PWD(outToClient, curDir);
		else if (action.equals("CWD"))
		{
			if (num > 1)
				TcurDir = new ftp_server().CWD(outToClient, curDir, parameter);	//return the new PWD
			return TcurDir;
		}
		else if (action.equals("MKD"))
		{
			if (num > 1)
				new ftp_server().MKD(outToClient,curDir, parameter);
		}
		else if (action.equals("RMD"))
		{
			if (num > 1)
				new ftp_server().RMD(outToClient, curDir, parameter);
		}
		else if (action.equals("TYPE"))
		{
			if (num > 1)
			{
				if (parameter.equals("I"))
				{
					System.out.println("200 Type set to I");
					outToClient.writeBytes("200 Type set to I\r\n");
				}
			}
			else
			{
				System.out.println("500 Syntax error, command unrecognized");
				outToClient.writeBytes("500 Syntax error, command unrecognized\r\n");
			}
		}
		else
		{
			System.out.println("502 Command( " + temp + " ) not implemented.");
			outToClient.writeBytes("502 Command( " + temp + " )not implemented.\r\n");
		}
	

		/*
		} while (!action.equals("QUIT"));

		System.out.println("someone quits");
		return true;
		 */
		return TcurDir;

	}

	//return the relative path (String)
	public String retRpath(File dir) throws IOException
	{
		File file = dir;
		String root = new File(".").getCanonicalPath();
		System.out.println(root);
		String path = file.getAbsolutePath();
		String rpc = new String("\\"); //useful
		System.out.println(path);

		path = path.substring(root.length());
		System.out.println(path);
		StringTokenizer pathC = new StringTokenizer(path, rpc);
		int num = pathC.countTokens();
		System.out.println(num);



		if (num > 1)
			path = pathC.nextToken();
		System.out.println(path);
		for (int i = 0; i < num -1; i++)
		{
			path = new String(path + "/" + pathC.nextToken());
		}

		if (path.startsWith("\\"))
			path = path.substring(1);

		if (!path.startsWith("/"))
			path = new String("/" + path);
		return path;
	}

	//for current directory command
	public boolean PWD(DataOutputStream outToClient, File curDir) throws IOException
	{
		System.out.println("257 \"" + retRpath(curDir) + "\" is current directory");
		outToClient.writeBytes("257 \"" + retRpath(curDir) + "\" is current directory\r\n");
		return true;

	}

	//for changing directory command
	public File CWD(DataOutputStream outToClient, File curDir, String chDir) throws IOException
	{
		File file;
		if (chDir.startsWith("/"))
			file = new File(new File(".").getCanonicalPath() + chDir);
		else
			file = new File(curDir, chDir);

		String ck1, ck2;
		ck1 = new String(new File(".").getCanonicalPath());
		ck2 = file.getAbsolutePath();

		if (!ck2.startsWith(ck1))
			file = new File(new File(".").getAbsolutePath());

		//-----------------------------
		
		if (file.exists() && file.isDirectory())
		{
			outToClient.writeBytes("250 CWD successful. \"" + retRpath(file) + "\" is current directory\r\n");
			System.out.println("250 CWD successful. \"" + retRpath(file) + "\" is current directory\r\n");
			return file;
		}
		else
		{
			System.out.println("501 Syntax error in parameters or arguments.");
			outToClient.writeBytes("501 Syntax error in parameters or arguments.\r\n");
			return curDir;
		}
	}

	//for making directory command
	public boolean MKD(DataOutputStream outToClient, File curDir, String mkDir) throws IOException
	{
		
		File file;
		if (mkDir.startsWith("/"))
			file = new File(new File(".").getCanonicalFile(), mkDir);
		else
			file = new File(curDir, mkDir);

		System.out.println(file.getAbsolutePath());

		if (file.exists() && file.isDirectory())
		{
			System.out.println("550 Directory already exists");
			outToClient.writeBytes("550 Directory already exists\r\n");
			return true;
		}

		if (file.mkdir())
		{
			System.out.println("257 Directory created successfully");
			outToClient.writeBytes("257 Directory created successfully\r\n");
			return true;
		}
		else
		{
			System.out.println("550 Requested action not taken");
			outToClient.writeBytes("550 Requested action not taken\r\n");
		}
		return false;
	}

	//for removing directory command
	public boolean RMD(DataOutputStream outToClient, File curDir, String rmDir) throws IOException
	{
		File file;
		if (rmDir.startsWith("/"))
			file = new File(new File(".").getCanonicalFile(), rmDir);
		else
			file = new File(curDir, rmDir);

		if (file.exists())
		{
			if (file.isDirectory())
			{
				if (file.delete())
				{
					outToClient.writeBytes("250 Directory deleted successfully\r\n");
					return true;
				}
				else
					outToClient.writeBytes("550 Requested action not taken\r\n");
			}
			else
				outToClient.writeBytes("550 It is not a directory\r\n");
		}
		else
			outToClient.writeBytes("550 The directory isn't exists\r\n");
		return false;
	}
			

	//for entering passive mode & checking the correlative command
	public boolean PASV(Socket serverSocket, BufferedReader inFromClient, DataOutputStream outToClient, File curDir) throws IOException
	{
		ServerSocket dataSocket = new ServerSocket();
		dataSocket.bind(null);

		String lIP = serverSocket.getLocalAddress().getHostAddress();
		//String lIP = dataSocket.getInetAddress().getHostAddress();
		if (lIP.equals("0:0:0:0:0:0:0:1") || lIP.equals("0.0.0.0"))
			lIP = "127,0,0,1";
		else
		{
			StringTokenizer tokenizer = new StringTokenizer(lIP, ".");
			lIP = tokenizer.nextToken() + "," + tokenizer.nextToken() + "," + tokenizer.nextToken() + "," + tokenizer.nextToken();
		}
		int port1, port2;

		port2 = dataSocket.getLocalPort();
		port1 = port2 / 256;
		port2 = port2 % 256;

		System.out.println("227 Entering Passive Mode (" + lIP + "," + port1 + "," + port2 + ")");
		outToClient.writeBytes("227 Entering Passive Mode (" + lIP + "," + port1 + "," + port2 + ")\r\n");

		Socket dataConnect = dataSocket.accept();

		//-------------- get action ----------------------------
		System.out.println("150 Connection accepted");
		outToClient.writeBytes("150 Connection accepted\r\n");

		/*
		String temp = inFromClient.readLine();
		String action;

		StringTokenizer actionC = new StringTokenizer(temp, " ");
		int num = actionC.countTokens();
		action = actionC.nextToken();
		 */
		String temp = inFromClient.readLine();
		String action;
		String parameter = null;

		StringTokenizer actionC = new StringTokenizer(temp, " ");
		int num = actionC.countTokens();
		action = actionC.nextToken();

		if (num > 1)
		{
			parameter = actionC.nextToken();

			while (actionC.hasMoreTokens())
			{
				parameter = new String(parameter + " " + actionC.nextToken());
			}
		}

		File file = curDir;
		String filename;

		if (action.equals("LIST"))
		{
			System.out.print("LIST");
			
			if (parameter != null)
			{
				String path = parameter;
				file = new File(curDir, path);
				System.out.println(" " +path);
			}
			
			System.out.print("\n");

			return list(dataConnect, file, outToClient);
		}
		if (action.equals("NLST"))
		{
			System.out.print("NLST");

			if (parameter != null)
			{
				String path = parameter;
				file = new File(curDir, path);
				System.out.println(" " + path);
			}
			
			System.out.print("\n");
			
			return getname(dataConnect, file, outToClient);
		}
		else if (action.equals("STOR"))
		{
			if (parameter == null)
			{
				System.out.println("500 Syntax error, command unrecognized");
				outToClient.writeBytes("500 Syntax error, command unrecognized\r\n");
				return false;
			}
			filename = parameter;

			System.out.println("STOR " + filename);
			return fileFromClient(dataConnect, filename, curDir, outToClient);
		}
		else if (action.equals("RETR"))
		{
			if (parameter == null)
			{
				System.out.println("500 Syntax error, command unrecognized");
				outToClient.writeBytes("500 Syntax error, command unrecognized\r\n");
				return false;
			}
			filename = parameter;

			System.out.println("RETR " + filename);
			return fileToClient(dataConnect, filename, curDir, outToClient);
		}
		//---------------------------------------------------------------

		System.out.println("500 Syntax error, command unrecognized");
		outToClient.writeBytes("500 Syntax error, command unrecognized\r\n");
		return false;
	}
	
	public boolean getname(Socket dataSocket, File file, DataOutputStream outToClient) throws IOException
	{

		if (!file.exists() || !file.isDirectory())
		{
			outToClient.writeBytes("426 Connection closed; transfer aborted.\r\n");
			System.out.println("426 Connection closed; transfer aborted.\r\n");
			dataSocket.close();
			return false;
		}

		DataOutputStream sendList = new DataOutputStream(dataSocket.getOutputStream());
		
		String temp;
		File[] list = file.listFiles();			// get list of files;
		for (int i = 0; i < list.length; i++)
		{
			sendList.writeBytes(list[i].getName() + "\r\n");
			
		}
		sendList.flush();
		sendList.close();
		dataSocket.close();
		
		return true;
	}
	
	//for listing directory
	public boolean list(Socket dataSocket, File file, DataOutputStream outToClient) throws IOException
	{
		if (!file.exists() || !file.isDirectory())
		{
			outToClient.writeBytes("426 Connection closed; transfer aborted.\r\n");
			System.out.println("426 Connection closed; transfer aborted.\r\n");
			dataSocket.close();
			return false;
		}

		DataOutputStream sendList = new DataOutputStream(dataSocket.getOutputStream());
		/*
		if (System.getProperty("os.name").endsWith("Linux"))
		{
			String temp = null;
			String[] cmd = { "/bin/ls", file.getAbsolutePath(), "-al" };
			Process is_proc = Runtime.getRuntime().exec(cmd);
			DataInputStream is_in = new DataInputStream(is_proc.getInputStream());

			while ((temp = is_in.readLine()) != null)
				sendList.writeBytes(temp);
		}
		else
		{
		*/
			String temp;
			File[] list = file.listFiles();			// get list of files;
			for (int i = 0; i < list.length; i++)
			{
				if (list[i].isFile())
					temp = "-";
				else
					temp = "d";
				sendList.writeBytes(temp + "\t" + list[i].length() + "\t" + list[i].getName() + "\n");

			}
		//}
		sendList.flush();
		sendList.close();
		dataSocket.close();
		
		return true;
	}

	//for client to upload file
	public boolean fileFromClient(Socket dataSocket, String filename, File curDir, DataOutputStream outToClient)
	{
		try
		{
			File file;
			if (filename.startsWith("/"))
				file = new File(new File(".").getCanonicalFile(), filename);
			else
				file = new File(curDir, filename);

			if (file.exists() && !file.isFile())
			{
				if (!file.createNewFile())
				{
					outToClient.writeBytes("426 Connection closed; transfer aborted.\r\n");
					System.out.println("426 Connection closed; transfer aborted.\r\n");
					dataSocket.close();
					return false;
				}

			}


			DataOutputStream outToFile = new DataOutputStream(new FileOutputStream(file));
			DataInputStream getFile = new DataInputStream(dataSocket.getInputStream());

			int recv = 0;
			int bufferSize = 1024;
			byte[] buffer = new byte[bufferSize];

			while ((recv = getFile.read(buffer, 0, bufferSize)) != -1)		//getting the file
			{
				outToFile.write(buffer, 0, recv);
			}
			outToFile.flush();
			outToFile.close();
			dataSocket.close();

			return true;
		}
		catch (Exception e)
		{
		}

		return false;
	}

	//for client to download file
	public boolean fileToClient(Socket dataSocket, String filename, File curDir, DataOutputStream outToClient)
	{
		try
		{
			File file;
			if (filename.startsWith("/"))
				file = new File(new File(".").getCanonicalFile(), filename);
			else
				file = new File(curDir, filename);

			if (!file.exists() || !file.isFile())
			{
				outToClient.writeBytes("426 Connection closed; transfer aborted.\r\n");
				System.out.println("426 Connection closed; transfer aborted.\r\n");
				dataSocket.close();

				return false;
			}

			// uploading file to client --------------------------------------------------
			BufferedOutputStream outData = new BufferedOutputStream(dataSocket.getOutputStream());

			FileInputStream inFile = new FileInputStream(file.getAbsolutePath());
			int numOfBytes = 1024;
			byte[] fileInBytes = new byte[numOfBytes];
			while (inFile.read(fileInBytes) != -1)
			{
				outData.write(fileInBytes, 0, numOfBytes);
			}
			inFile.close();

			outData.flush();
			outData.close();
			dataSocket.close();
			// ---------------------------------------------------------------------------

			return true;
		}
		catch (Exception e)
		{

		}
		return false;
	}

	// ---------------- thread ---------------------
	class ServerThread extends Thread
	{
		Socket connectionSocket;
		File curDir;

		public ServerThread(Socket connectionSocket)
		{
			this.connectionSocket = connectionSocket;
			
		}

		public void run()
		{
			try
			{

				curDir = new File(new File(".").getCanonicalPath()); // store where is the current directory

				BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));  //input data from client
				DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream());					//output data to client


				//--------------------- welcome msg---------------
				outToClient.writeBytes("200 Welcome.\r\n");
				outToClient.flush();

				//----------------------------------------

				String action;				//store the command
				String parameter = null;	//store the parameter
				String temp;				//for reading command and parament
				StringTokenizer actionC;


				// for command USER
				// break until corroct input
				do
				{
					temp = inFromClient.readLine();
					actionC = new StringTokenizer(temp, " ");
					int num = actionC.countTokens();
					action = actionC.nextToken();
					if (num > 1)
						parameter = actionC.nextToken();

					if (action.equals("USER"))
					{
						if (num > 1)
						{
							outToClient.writeBytes("331 Password required for " + parameter + "\r\n");
							break;
						}
						else
							outToClient.writeBytes("530 Please log in with USER and PASS first\r\n");
					}
				} while (true);

				// for command PASS
				// break until corroct input
				do
				{
					boolean bad = true;

					temp = inFromClient.readLine();
					actionC = new StringTokenizer(temp, " ");
					int num = actionC.countTokens();
					action = actionC.nextToken();

					if (action.equals("PASS"))
					{
						if (num > 1)
						{
							outToClient.writeBytes("230 Logged on\r\n");
							break;
						}
						else
							outToClient.writeBytes("530 Please log in with PASS first\r\n");
					}
				} while (true);

				// read command until QUIT;
				temp = inFromClient.readLine();
				System.out.println(temp);
				while (!temp.equals("QUIT")){
					
					curDir = new ftp_server().readCommand(connectionSocket, temp, inFromClient, outToClient, curDir);
					//System.out.println(curDir.getPath());
					temp = inFromClient.readLine();
					System.out.println(temp);
				}
			}
			catch (Exception e)
			{
				System.out.println("Error: " + e);
			}
		}
	}
}

